package networkLoader;

import com.google.common.collect.Lists;
import network.Device;
import network.Network;
import network.Rule;
import verifier.APKeepVerifier;
import verifier.Ports.ArrayPorts;
import verifier.util.BDDEngine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

public class FattreeNetwork {
    private static final int npod = 112;

    private static Network buildTopology(int k) {
        Network n = new Network();
        //// create devices
        for (int iPod = 0; iPod < k; iPod++) {
            for (int j = 0; j < k / 2; j++) {
                Device device = n.addDevice("rsw-" + iPod + "-" + j);
                // add rsw -> host port
                device.addPort(String.format("rsw-%d-%d>h-%d-%d", iPod, j, iPod, j));
            }

            for (int j = 0; j < k / 2; j++) {
                n.addDevice("fsw-" + iPod + "-" + j);
            }
        }
        for (int iSsw = 0; iSsw < k * k / 4; iSsw++) {
            n.addDevice("ssw-" + iSsw);
        }
        //// \create devices

        //// create links
        for (int iPod = 0; iPod < k; iPod++) {
            for (int iFsw = 0; iFsw < k / 2; iFsw++) {
                String fsw = "fsw-" + iPod + "-" + iFsw;
                // down links
                for (int iRsw = 0; iRsw < k / 2; iRsw++) {
                    String rsw = "rsw-" + iPod + "-" + iRsw;
                    n.addLink(fsw, fsw + ">" + rsw, rsw, rsw + ">" + fsw);
                }
                // up links
                for (int iSsw = iFsw * k / 2; iSsw < iFsw * k / 2 + k / 2; iSsw++) {
                    String ssw = "ssw-" + iSsw;
                    n.addLink(fsw, fsw + ">" + ssw, ssw, ssw + ">" + fsw);
                }
            }
        }
        //// \create links
        for (Device device : n.getAllDevices()) device.uid = Device.cnt++;
        return n;
    }

    public static Network getNetwork(int npod) {
        Network n = buildTopology(npod);

        //// create rules
        // first 8 bits is pod id, next 8 bit is index of rsw in pod
        for (int iPod = 0; iPod < npod; iPod++) {
            for (int iRsw = 0; iRsw < npod/2; iRsw++) {
                String rsw = "rsw-" + iPod + "-" + iRsw;
                Device device = n.getDevice(rsw);
                for (int jPod = 0; jPod < npod; jPod++) {
                    for (int jRsw = 0; jRsw < npod/2; jRsw++) {
                        String dstrsw = "rsw-" + jPod + "-" + jRsw;
                        long dstip = (jPod << 24) + ((jRsw + 1) << 16);
                        Rule rule;
                        if (dstrsw.equals(rsw)) {
                            rule = new Rule(device, dstip, 16, device.getPort(rsw + ">h-" + jPod + "-" + jRsw));
                        } else {
                            String dstfsw = "fsw-" + iPod + "-" + (npod/2 * jPod + jRsw) % (npod/2);
                            rule = new Rule(device, dstip, 16, device.getPort(rsw + ">" + dstfsw));
                        }
                        n.addInitialRule(rule);
                        device.addInitialRule(rule);
                    }
                }
            }
            for (int iFsw = 0; iFsw < npod/2; iFsw++) {
                String fsw = "fsw-" + iPod + "-" + iFsw;
                Device device = n.getDevice(fsw);
                for (int jPod = 0; jPod < npod; jPod++) {
                    for (int jRsw = 0; jRsw < npod/2; jRsw++) {
                        String dstrsw = "rsw-" + jPod + "-" + jRsw;
                        long dstip = (jPod << 24) + ((jRsw + 1) << 16);
                        Rule rule;
                        if (jPod == iPod) { // intra pod
                            rule = new Rule(device, dstip, 16, device.getPort(fsw + ">" + dstrsw));
                        } else {
                            String dstssw = "ssw-" + (iFsw * (npod/2) + ((npod/2) * jPod + jRsw) % (npod/2));
                            rule = new Rule(device, dstip, 16, device.getPort(fsw + ">" + dstssw));
                        }
                        n.addInitialRule(rule);
                        device.addInitialRule(rule);
                    }
                }
            }
        }

            for (int iSsw = 0; iSsw < npod*npod/4; iSsw++) {
                String ssw = "ssw-" + iSsw;
                Device device = n.getDevice(ssw);
                for (int k = 0; k < npod; k++) {
                    for (int l = 0; l < npod/2; l++) {
                        long dstip = (k << 24) + ((l + 1) << 16);
                        String dstfsw = "fsw-" + k + "-" + (iSsw / (npod/2));
                        Rule rule = new Rule(device, dstip, 16, device.getPort(ssw + ">" + dstfsw));
                        n.addInitialRule(rule);
                        device.addInitialRule(rule);
                    }
                }
        }
        //// \create rules

        return n;
    }

    public static HashMap<String, Network> getPartitions(Network network, int k, BDDEngine bddEngine) {
        HashMap<String, Network> nameToPartition = new HashMap<>();
        for (int iPod = 0; iPod < k; iPod++) {
            HashSet<Device> devicesInPartition = new HashSet<>();
            for (int iRsw = 0; iRsw < k / 2; iRsw++) {
                devicesInPartition.add(network.getDevice("rsw-" + iPod + "-" + iRsw));
                devicesInPartition.add(network.getDevice("fsw-" + iPod + "-" + iRsw));
            }
            Network subgraphNetwork = network.getSubNetwork(devicesInPartition);
//            SubgraphPartition subgraphPartition = new SubgraphPartition(subgraphNetwork, null, bddEngine);
            nameToPartition.put("pod-" + iPod, subgraphNetwork);
        }
        List<Device> ssws = new ArrayList<>();
        for (int iSsw = 0; iSsw < k*k/4; iSsw++) {
            ssws.add(network.getDevice("ssw-" + iSsw));
        }

        List<List<Device>> sswsPartitions = Lists.partition(ssws, k/2);
        int iSpine = 0;
        for (List<Device> devices : sswsPartitions) {
            Network subgraphNetwork = network.getSubNetwork(devices);
//            SubgraphPartition subgraphPartition = new SubgraphPartition(subgraphNetwork, null, bddEngine);
            nameToPartition.put("spine-" + iSpine, subgraphNetwork);
            iSpine++;
        }

        return nameToPartition;
    }

    public static void main(String[] args) {
        Network n = getNetwork(48);
        System.out.println(n.getAllDevices().size());
        System.out.println(n.getInitialRules().size());
        APKeepVerifier apkeep = new APKeepVerifier(n, new ArrayPorts());
        for (Rule rule : n.getInitialRules()) {
            apkeep.insertRule(new Rule(rule.getDevice(), rule.getMatch().longValue(), rule.getPrefix(), rule.getOutPort()));
            apkeep.update(true);
        }
        System.out.println(apkeep.predSize());
    }
}
